package com.nulldev.util.io.fastIO;

import java.io.PrintStream;
import java.util.LinkedList;

import com.nulldev.util.threading.v3.IWorker;
import com.nulldev.util.threading.v3.ThreadDispatcher;

/**
 * Solution for bottlenecked IO, via a seperate I/O thread provided by the threadIt! v3 API.
 * @author null
 * @since nullUtil 2.7 (early build 1.0)
 */
public class FastIO {
	
	private static final ThreadDispatcher td = new ThreadDispatcher(true);
	
	public static abstract class IOSubmission {
		public abstract void onCallable();
	}
	
	private final IWorker ioWorker;
	private boolean stop, paused;
	private final LinkedList<IOSubmission> submissions = new LinkedList<IOSubmission>();
	
	private static final FastIO instance = new FastIO();
	
	private FastIO() {
		this.ioWorker = new IWorker() {
			
			private int skippedIters = 1;
			
			@Override
			public void run() {
				while (true) {
					if (FastIO.this.stop) {
						break;
					}
					if (FastIO.this.submissions.isEmpty()) {
						if (this.skippedIters > 5) {
							this.skippedIters = 1;
						}
						try {
							FastIO.this.paused = true;
							Thread.sleep(20L * this.skippedIters);
							this.skippedIters += 1;
							FastIO.this.paused = false;
						}catch (InterruptedException ie) {
							FastIO.this.paused = false;
						}
					}else {
						if (FastIO.this.submissions.isEmpty()) {
							continue;
						}
						final IOSubmission iosub = FastIO.this.submissions.removeFirst();
						iosub.onCallable();
						try {
							Thread.sleep(10L);
						} catch (InterruptedException e) {
						}
					}
				}
			}
			
			@Override
			public void shutdown() {
			}
			
			@Override
			public boolean isAlive() {
				return true;
			}
			
			@Override
			public String getName() {
				return "FastIO.ioThread";
			}
			
			@Override
			public int getID() {
				return ThreadDispatcher.randomID();
			}
		};
		td.addTask(ioWorker);
				/*new Thread(new Runnable() {
			
			@Override
			public void run() {
				while (true) {
					if (FastIO.this.stop) {
						break;
					}
					if (FastIO.this.submissions.isEmpty()) {
						try {
							FastIO.this.paused = true;
							Thread.sleep(100L);
							FastIO.this.paused = false;
						}catch (InterruptedException ie) {
							FastIO.this.paused = false;
						}
					}else {
						final IOSubmission iosub = FastIO.this.submissions.removeFirst();
						iosub.onCallable();
					}
				}
			}
		}, "FastIO API Thread");*/
	}
	
	public boolean submit(final IOSubmission submission) {
		final boolean result = this.submissions.add(submission);
		return result;
	}
	
	public void println(final String text) {
		this.submit(new IOSubmission() {
			
			@Override
			public void onCallable() {
				synchronized (System.err) {
					synchronized (System.out) {
						System.out.println(text);
					}
				}
			}
		});
	}
	
	public void println(final Object text) {
		this.submit(new IOSubmission() {
			
			@Override
			public void onCallable() {
				synchronized (System.err) {
					synchronized (System.out) {
						System.out.println(text);
					}
				}
			}
		});
	}
	
	public void print(final String text) {
		this.submit(new IOSubmission() {
			
			@Override
			public void onCallable() {
				synchronized (System.err) {
					synchronized (System.out) {
						System.out.print(text);
					}
				}
			}
		});
	}
	
	public void print(final Object text) {
		this.submit(new IOSubmission() {
			
			@Override
			public void onCallable() {
				synchronized (System.err) {
					synchronized (System.out) {
						System.out.print(text);
					}
				}
			}
		});
	}
	
	public void println(final PrintStream outputDevice, final String text) {
		this.submit(new IOSubmission() {
			
			@Override
			public void onCallable() {
				synchronized (outputDevice) {
					outputDevice.println(text);
				}
			}
		});
	}
	
	public void println(final PrintStream outputDevice, final Object text) {
		this.submit(new IOSubmission() {
			
			@Override
			public void onCallable() {
				synchronized (outputDevice) {
					outputDevice.println(text);
				}
			}
		});
	}
	
	public void print(final PrintStream outputDevice, final String text) {
		this.submit(new IOSubmission() {
			
			@Override
			public void onCallable() {
				synchronized (outputDevice) {
					outputDevice.print(text);
				}
			}
		});
	}
	
	public static FastIO get() {
		return instance;
	}
}
